-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow sign-to-contract commitments in schnorrsigs #588
Conversation
src/secp256k1.c
Outdated
@@ -610,6 +610,103 @@ int secp256k1_ec_pubkey_combine(const secp256k1_context* ctx, secp256k1_pubkey * | |||
return 1; | |||
} | |||
|
|||
/* Compute an ec commitment tweak as hash(pubkey, data). */ | |||
int secp256k1_ec_commit_tweak(const secp256k1_context *ctx, unsigned char *tweak32, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be static
If you want to harden against misuse of the nonce function arguments, you could add a constant uint64 as the first member of the s2c_commit_context and test it. Generally, I'd like to see us do that with all objects that we allow users to pass around: set a magic value when they're constructed, zero it when they're destroyed. Check for it before other accesses... exception being purely internal objects or elements where a caller might plausibly have thousands of them in existence at once (such that the overhead becomes a concern).
Odd that we didn't document it-- aux data is actually part of RFC6979. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, I wonder whether it is a better idea
- not to use the nonce function for s2c. (For example, this prevents people from using their own nonce function together with s2c.)
- instead of a s2c context, have a s2c_opening, which contains all opening information (i.e., the original pubnonce and the information whether the nonce was negated. This looks more like a normal commitment API to me then.)
|
||
if (data_size == 0) { | ||
/* That's probably not what the caller wanted */ | ||
return 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If that's not what the caller wanted, maybe a CHECK or a VERIFY_CHECK is better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VERIFY_CHECK only works in tests. ARG_CHECK crashes the caller's program. Not sure if that would be better given that there doesn't really seem to be precedent for aborting in such cases in libsecp. Also I don't see a good reason for aborting as this condition can be trivially recovered from (by just returning 0).
src/tests.c
Outdated
/* Create random data */ | ||
{ | ||
secp256k1_scalar d; | ||
random_scalar_order_test(&d); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
secp256k1_rand256 instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
secp256k1_gej_add_ge(&pj, &pj, &p); | ||
secp256k1_pubkey_load(ctx, &p, commitment); | ||
secp256k1_ge_neg(&p, &p); | ||
secp256k1_gej_add_ge(&pj, &pj, &p); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it's a good idea to implement an gej_cmp or even pubkey_cmp... No idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, good idea. Maybe not in this PR.
include/secp256k1.h
Outdated
* | ||
* The exact representation of data inside is implementation defined and not | ||
* guaranteed to be portable between different platforms or versions. It is however | ||
* guaranteed to be 128 bytes in size, and can be safely copied/moved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one paragraph above, you say that copying is discouraged
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch. I think there's no issue with copying that structure and I think I just copied this sentence from the MuSig session without thinking about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
sig_tmp = sig; | ||
secp256k1_rand256(&sig_tmp.data[0]); | ||
CHECK(secp256k1_schnorrsig_verify_s2c_commit(ctx, &sig_tmp, data32, &s2c_original_nonce, nonce_is_negated) == 0); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there should be a test that verification fails when nonce_is_negated is wrong
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
I added a commit that adds a magic number to the sign to contract context struct. |
…eaks of public keys. The functionality is not exposed.
…chnorr do sign-to-contract commitments if an s2c context is provided as nonce data
e726610
to
8fa102c
Compare
squashed |
ARG_CHECK(s2c_ctx != NULL); | ||
ARG_CHECK(data32 != NULL); | ||
|
||
memcpy(s2c_ctx->magic, &s2c_commit_context_magic, sizeof(s2c_ctx->magic)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably it's just me being not familiar enough with C but why copy from a uint64_t
to a unsigned char[8]
instead of just putting a uint64_t
in the struct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was some reason I don't remember to use uint64_t
. But I think using a byte array in the struct is arbitrary... probably easier to read if replaced by uint64_t
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another thing, I'm pretty sure memcpy from uint64_t
to unsigned char[8]
is platform defined(BE/LE)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but not really an issue as the doc clearly states
The exact representation of data inside is implementation defined and not guaranteed to be portable between different platforms or versions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't matter in the end but the code is certainly easier to read if it's the same type twice (no matter what type that is)
if (data != NULL) { | ||
/* Create a sign-to-contract commitment by setting nonce32 <- nonce32 + | ||
* hash(nonce32*G, s2c_ctx->data) */ | ||
secp256k1_s2c_commit_context *s2c_ctx = (secp256k1_s2c_commit_context *)data; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should verify the magic number here and fail if not right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, good catch
unsigned char magic[8]; | ||
unsigned char data[32]; | ||
unsigned char data_hash[32]; | ||
secp256k1_pubkey original_pubnonce; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should the "public nonce" really be represented as pubkey
? for me ge
or gej
would be more readable, but they're probably not exported....
(I got confused while diving into secp256k1_ec_commit_seckey
and secp256k1_ec_commit_tweak
about the whole public key thing)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ge
or gej
are not exported. But, hm, perhaps this should be a pubnonce type if it already confuses people.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, yes. A public key is a public key, and a nonce is a different thing. (I mean we can't enforce anything in C but maybe it's helpful to use different names for the two public types at least.)
Do we have the same problem in other places too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On a second thought... if we really want to optimize for a simple API, then #589 is the way to go. There the entire problem is eliminated by hiding from the user that a signature contains a "nonce".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The nonce is represented as a pubkey also there https://github.com/bitcoin-core/secp256k1/pull/589/files#diff-222d6d707e263c79c462fb223b0e3ddcR98
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay, yes, but that struct is not really public:
https://github.com/bitcoin-core/secp256k1/pull/589/files#diff-222d6d707e263c79c462fb223b0e3ddcR87
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fair
Closing in favor of #589. Thanks for the feedback. |
This PR implements creation and verification of sign-to-contract commitments in the
schnorrsig module. The following snippet for example puts a commitment to
data32
into the (Schnorr) signature.
This PR is based on #558. The commits in this PR was previously included in #572.
The tests should provide full coverage of the reachable lines.
There is also functionality for doing pay-to-contract style commitments
(ec_commit), but it's not exposed yet. If it will be in the future it would
make sense to add an optional argument with a SHA midstate to allow hashing
arbitrary version prefixes before hashing the public key (which would be useful
in taproot for example).
Sign-to-contract and right now only work with schnorrsig_sign and
nonce_function_bipschnorr. It should be straightforward to add support for
ECDSA too in the future.
One thing to keep in mind is that for some users moving from ecdsa to
schnorrsigs requires reading the documentation carefully. There is undocumented
functionality in nonce_function_rfc6979 where the additional nonce data is
hashed into the secret. If someone uses that and then call schnorrsig_sign with
similar nonce data this results in a segmentation fault because
nonce_function_bipschnorr which is used by default in schnorrsig_sign expects
an s2c_commit_context object as nonce data and writes back to it.